#!/usr/bin/python
from visual import *

class Pos(object):
	def __init__(self,arg1,arg2=None):
		if arg2 is None:
			loc = arg1
		else:
			loc = (arg1,arg2)

		if type(loc) == tuple: # if we got x and y
			self.x = loc[0]
			self.y = loc[1]
			self.file = chr(97+self.x)
			self.rank = str(self.y+1)
		elif type(loc) == str: # if we got math notation string a1->h8
			self.file = loc[0]
			self.rank = loc[1]
			self.x = ord(self.file.lower())-97
			self.y = int(self.rank)-1

	def isOnBoard(self):
		return (0<=self.x and self.x<8 and 0<=self.y and self.y<8)

	def __eq__(self,other):
		return ((self.x == other.x) and (self.y == other.y))

	def __hash__(self):
		"""Function to allow positions to be used as keys in a dictionary"""
		return (self.x*8)+self.y

	def __str__(self):
		return "%s%s" % (self.file, self.rank)


# BOARD OBJECT

class Board(object):
	def __init__(self):
		self.data = dict()
		self.selected = None

		self.fill()

	def fill(self):
		for color, y1, y2 in [('b',0,1),('w',7,6)]:
			for x in range(8):
				self.addPiece(color,Pawn,Pos((x,y2)))
			for x, pieceType in enumerate([Rook,Knight,Bishop,
												Queen,King,
											Bishop,Knight,Rook]):
				self.addPiece(color,pieceType,Pos((x,y1)))

		self.addPiece("w",Knight,Pos("c5"))
		self.addPiece("w",Rook,Pos("f5"))
		self.addPiece("w",Queen,Pos("d4"))
		self.addPiece("w",King,Pos("f3"))

	def __getitem__(self,key,default=None):
		return self.data.get(key,default)

	def __setitem__(self,key,value):
		self.data[key] = value

	def __str__(self):
		string =	"    a b c d e f g h \n"
		string +=	"    _______________ \n"
		for rank in "87654321":
			string += "%s  " % rank
			for file in "abcdefgh":
				pos = Pos(file+rank)
				startcode = ""
				if self.selected:
					if (pos == self.selected.pos):
						startcode += "\033[0;44m"
					if pos in self.selected.getMoves():
						startcode += "\033[0;42m"
						if self[pos] and self[pos].color != self.selected.color:
							startcode += "\033[0;41m"
				endcode = "\033[0;0m"
				string += "|%s%s%s" % (startcode,self[pos] or "_", endcode)
			string += "|  %s\n" % rank
		string +=	"\n"
		string +=	"    a b c d e f g h"
		return string

	def addPiece(self, color, pieceType, pos):
		self.data[pos] = pieceType(color, pos, self)

	def makeQueen(self, pPos):
		pawn = self.data[pPos]
		if (type(pawn) == Pawn):
			if	((pawn.color == 'b') and (pPos.y == 7)) or ((pawn.color == 'w') and (pPos.y == 0)):
				self.data[pPos] = None
				self.addPiece(pawn.color,Queen,pPos)
				del pawn


	def movePiece(self, startpos, endpos):
		movingPiece = self[startpos]
		targetPiece = self[endpos]

		if movingPiece is None:
			print "No piece at %s" % startpos
			return False
		if endpos not in movingPiece.getMoves():
			print "%s cannot move to %s" % (movingPiece, endpos)
			return False

		if targetPiece is not None:
			if targetPiece.color == movingPiece.color:
				print "%s cannot move to %s, a piece is already there." % (movingPiece, endpos)
				return False
			else: # There's an enemy piece there
				print "%s takes %s" % (movingPiece.humanString(), targetPiece.humanString())
				del targetPiece

		self[endpos] = movingPiece
		movingPiece.setPos(endpos)
		if type(movingPiece) == Pawn:
			movingPiece.youJustMoved()

		self[startpos] = None
		return True

class PieceModel(frame):
	def __init__(self, *args, **kwargs):
		super(frame, self).__init__(*args, **kwargs)
		self.color = kwargs['color']

class Piece(object):
	def __init__(self, color, pos, board):
		"""color should be either 'b' or 'w'"""
		self.color = color.lower()
		self.pos = pos
		self.char = "error: Piece of no type"
		self.forwards = -1 if self.color == 'w' else 1

		# The `Board` object the piece is on
		self.board = board

	def getMoves(self):
		"""Return a list of possible positions to move to"""
		#empty, implemented by subclass
		pass

	def setPos(self, newpos):
		self.pos = newpos

	def getMovesInDirections(self,directions):
		moves = []
		for dx, dy in directions:
			nx = self.pos.x
			ny = self.pos.y
			for i in range(8):
				nx += dx
				ny += dy
				npos = Pos(nx,ny)
				if not npos.isOnBoard(): break

				piece = self.board[npos]
				if piece is None:
					moves.append(npos)
				else:
					if piece.color != self.color:
						moves.append(npos)
					break
		return moves

	def __str__(self):
		if self.color=='b': return self.char.lower()
		else: return "\033[1m"+self.char.upper()+"\033[0;0m"

	def humanString(self):
		if self.color=='b': col = "Black"
		else: col = "White"
		return "%s %s" % (col, self.name)

	def makePieceModel(self):
		if self.color=='b':	col = (.2,.2,.2)
		else:				col = (1,1,1)

		obj = PieceModel(pos=(self.pos.x,0,self.pos.y), color=self.color)
		for prim, args in self.geometry:
			visual.__dict__[prim](frame=obj, color=col,**args)
		return obj



class Pawn(Piece):
	def __init__(self, *args):
		super(Pawn,self).__init__(*args)
		self.char = 'p'
		self.name = "Pawn"
		self.hasMoved = False

		self.geometry = [
							("cone",	{"pos":(0,0,0), "axis":(0,.5,0), "radius":.3}),
							("sphere",	{"pos":(0,.5,0), "radius":.15})
						]

	def youJustMoved(self):
		self.hasMoved = True
			
	def getMoves(self):
		"""Show me your moves"""
		moves = []
		pos1 = Pos((self.pos.x,self.pos.y+self.forwards))
		pos2 = Pos((self.pos.x,self.pos.y+(self.forwards*2)))
		if pos1.isOnBoard():				#if one ahead is on the board
			if self.board[pos1] is None:	#and it's empty
				moves.append(pos1)			#we can move there
				#if we haven't moved yet and 2 ahead is free, we can go there
				if not self.hasMoved and (self.board[pos2] is None):
					moves.append(pos2)
		else: #1 ahead is off the board, we are queening
			print "I am now a queen. Whaaaat"
			self.board.makeQueen(self.pos)

		# The taking positions
		for tpos in [	Pos((self.pos.x+1,self.pos.y+self.forwards)),
						Pos((self.pos.x-1,self.pos.y+self.forwards))]:
			piece = self.board[tpos]
			if (piece is not None) and (piece.color != self.color):
				moves.append(tpos)
			
		return moves


class Rook(Piece):
	def __init__(self, *args):
		super(Rook,self).__init__(*args)
		self.char = 'r'
		self.name = "Rook"

		csize = .15 #crenelation size
		self.geometry = [
							("cylinder",	{"pos":(0,0,0), "axis":(0,.7,0), "radius":.3}),
							("box",	{"pos":(+.3-(csize/2),.7+(csize/2),0), "width":csize, "height":csize, "length":csize}),
							("box",	{"pos":(-.3+(csize/2),.7+(csize/2),0), "width":csize, "height":csize, "length":csize}),
							("box",	{"pos":(0,.7+(csize/2),+.3-(csize/2)), "width":csize, "height":csize, "length":csize}),
							("box",	{"pos":(0,.7+(csize/2),-.3+(csize/2)), "width":csize, "height":csize, "length":csize}),
						]

	def getMoves(self):
		return self.getMovesInDirections([(1,0),(-1,0),(0,1),(0,-1)])

class Knight(Piece):
	def __init__(self, *args):
		super(Knight,self).__init__(*args)
		self.char = 'n'
		self.name = "Knight"
		self.geometry = [
							("box",	{"pos":(0,.4,0), "width":.3, "height":.8, "length":.3}),
							("box",	{"pos":(0,.5,self.forwards*.2), "axis":(0,-1,self.forwards),
										"width":.3, "height":.3, "length":.5}),
						]

	def getMoves(self):
		dirs = [(-1,-2),(-1,2),(1,-2),(1,2),(-2,-1),(-2,1),(2,-1),(2,1)]
		moves = []
		for dx, dy in dirs:
			npos = Pos((self.pos.x+dx, self.pos.y+dy))
			if npos.isOnBoard():
				piece = self.board[npos]
				if piece is None or (piece.color != self.color):
					moves.append(npos)
		return moves
			

class Bishop(Piece):
	def __init__(self, *args):
		super(Bishop,self).__init__(*args)
		self.char = 'b'
		self.name = "Bishop"

		self.geometry = [
						("cylinder", {"pos":(0,0,0), "axis":(0,.6,0), "radius":.15}),
						("cone", {"pos":(0,.65,0), "axis":(0,.3,0), "radius":.2}),
						("cone", {"pos":(0,.65,0), "axis":(0,-.3,0), "radius":.2}),
						]
	def getMoves(self):
		return self.getMovesInDirections([(1,1),(1,-1),(-1,1),(-1,-1)])

class Queen(Piece):
	def __init__(self, *args):
		super(Queen,self).__init__(*args)
		self.char = 'q'
		self.name = "Queen"

		self.geometry = [
						("cylinder", {"pos":(0,0,0), "axis":(0,.7,0), "radius":.2} ),
						("cone",{"pos":(0,.9,0), "axis":(0,-.6,0), "radius":.3}),
						("sphere", {"pos":(0,.9,0), "radius":.05}),
						]

	def getMoves(self):
		return self.getMovesInDirections(	[(1,1),(1,-1),(-1,1),(-1,-1),
											(1,0),(-1,0),(0,1),(0,-1)]	)

class King(Piece):
	def __init__(self, *args):
		super(King,self).__init__(*args)
		self.char = 'k'
		self.name = "King"

		self.geometry = [
						("cylinder", {"pos":(0,0,0), "axis":(0,.7,0), "radius":.2} ),
						("box", {"pos":(0,.85,0), "width":.1, "height":.1, "length":.3, "axis":(1,0,0)}),
						("box", {"pos":(0,.85,0), "width":.1, "height":.1, "length":.3, "axis":(0,1,0)}),
						]

	def getMoves(self):
		dirs = [(-1,-1),( 0,-1),( 1,-1),
				(-1, 0),        ( 1, 0),
				(-1, 1),( 0, 1),( 1, 1)]
		moves = []
		for dx, dy in dirs:
			npos = Pos((self.pos.x+dx, self.pos.y+dy))
			if npos.isOnBoard():
				piece = self.board[npos]
				if piece is None or (piece.color != self.color):
					moves.append(npos)
		return moves

import re

from visual import *
class GameController(object):
	def __init__(self):
		self.board = Board()
		self.currentColor = 'w'
		self.layers = []
		self.highlights = []
		self.t = -1


	def makeLayer(self):
		"""create 3d objects for a layer"""
		layer = []
		z = self.t
		layer.append(curve(pos=[(-.5,z,-.5),(-.5,z,7.5),(7.5,z,7.5),(7.5,z,-.5)]))
#		for x in range(8):
#			label(pos=(x,z,-1), text="abcdefgh"[x], box=False, opacity=0)
#			label(pos=(x,z,8), text="abcdefgh"[x], box=False, opacity=0)
		for y in range(8):
#			label(pos=(-1,z,y), text=(str)(y+1), box=False, opacity=0)
#			label(pos=(8,z,y), text=(str)(y+1), box=False, opacity=0)
			layer.append(curve(pos=[(-.5,z,y-.5),(7.5,z,y-.5)]))
			for x in range(8):
				layer.append(curve(pos=[(x-.5,z,-.5),(x-.5,z,7.5)]))
		self.layers.append(layer)

		# make the piece models
		for p in self.board.data.values():
			if p is not None:
				p.makePieceModel().y = self.t

	def clearHighlights(self):
		for highlight in self.highlights:
			highlight.visible = False
			del highlight
		self.highlights = []

	def makeMoveHighlights(self, moves):
		self.clearHighlights()
		for move in moves:
			self.highlights.append(box(pos=(move.x,self.t+0.01,move.y),length=1,height=.01,width=1,color=(1,0,0),opacity=0.5))


	def gameLoop(self):
		print self.board
		while True:
			self.t += 1
			print "Turn: %s" % self.t

			self.currentColor = 'w' if self.t % 2 == 0 else 'b'
			print "%s's Turn" % self.currentColor

			self.makeLayer()
			self.clearHighlights()

			scene.center = (3.5,self.t,3.5)

			moveMade = False
			while not moveMade:
				rate(20)
				while scene.mouse.clicked:
					evt = scene.mouse.getclick()
					obj = scene.mouse.pick
					if obj:
						print obj, obj.frame, type(obj.frame)
						if (type(obj.frame) == PieceModel):
							pm = obj.frame
							p = self.board[Pos((pm.x,pm.z))]
							print "That's a piece", pm, p,
							if pm.color == self.currentColor:
								if p != self.board.selected:
									self.board.selected = p
									scene.center = (p.pos.x,self.t+.5,p.pos.y)
									self.makeMoveHighlights(p.getMoves())
								else:
									self.board.selected = None
									self.clearHighlights()
								print self.board
						else: #it's a position
							startpos = self.board.selected.pos
							endpos = Pos((obj.x,obj.z))
							if self.board.movePiece(startpos, endpos):
								self.board.selected = None
								print self.board
								self.clearHighlights()
								moveMade = True
								break
							


				x = """inputText = raw_input(">")
				if re.match(r"^[a-h][1-8]$", inputText) is not None:
					p = self.board[Pos(inputText)]
					if p is not None:
						self.board.selected = p
						scene.center = (p.pos.x,self.t+.5,p.pos.y)
						self.makeMoveHighlights(p.getMoves())
						print self.board
					else:
						print "No piece there"
				elif re.match(r"^[a-h][1-8][a-h][1-8]$", inputText) is not None:
					startpos = Pos(inputText[:2])
					endpos = Pos(inputText[2:])

					if self.board[startpos]:
						if self.board[startpos].color != self.currentColor:
							print "It's %s's turn" % self.currentColor
						elif self.board.movePiece(startpos, endpos):
							self.board.selected = None
							print self.board
							break
					else:
						print "No piece at %s" % startpos
				else:
					print "Invalid Input"
					"""



import traceback
import sys

if __name__ == "__main__":
	try:
		gc = GameController()
		gc.gameLoop()
	except Exception as e:
		traceback.print_exc()
		sys.exit()
